\section{cmake}
\textbf{准备工作:}\\
首先,在cxxcode目录建立一个 cmake\_code目录,
用来放置我们学习过程中的所有练习。以后我们所有的 
cmake 练习都会放在cmake\_code 的子目录下(你也可以自行安排目录,
这个并不是限制,仅仅是为了叙述的方便)。
然后在 cmake 建立第一个练习目录ex1，在ex1目录建立 test1.cc 
和 CMakeLists.txt(注意文件名大小写)，
test1.cc 文件内容:
\cppfile{cxxcode/cmake_code/ex1/test1.cc}
CMakeLists.txt 文件内容:
\cmakefile{cxxcode/cmake_code/ex1/CMakeLists.txt}
\textbf{开始构建}\\
所有的文件创建完成后,ex1目录中应该存在 test1.cc 和 CMakeLists.txt 两个文件
接下来我们来构建这个工程,新建一个build目录将输出文件放到build中:
\begin{bash}
mkdir build
cd build 
cmake ..
\end{bash}
输出大概是这个样子 
\begin{bash}
-- compiler identification is GNU 6.3.0                                      
-- The CXX compiler identification is GNU 6.3.0                                    
-- Check for working C compiler: /usr/bin/cc                                       
-- Check for working C compiler: /usr/bin/cc -- works                              
-- Detecting C compiler ABI info         
-- Detecting C compiler ABI info - done  
-- Detecting C compile features          
-- Detecting C compile features - done   
-- Check for working CXX compiler: /usr/bin/c++                                    
-- Check for working CXX compiler: /usr/bin/c++ -- works                           
-- Detecting CXX compiler ABI info       
-- Detecting CXX compiler ABI info - done                                          
-- Detecting CXX compile features        
-- Detecting CXX compile features - done 
-- This is BINARY DIR /home/liushuai/linux/cxxcode/cmake_code/ex1/build            
-- This is SOURCE DIR /home/liushuai/linux/cxxcode/cmake_code/ex1                  
-- Configuring done 
-- Generating done  
-- Build files have been written to: /home/liushuai/linux/cxxcode/cmake_code/ex1/build                 
\end{bash}
再让我们看一下目录中的内容:
你会发现,系统自动生成了:\\*
\Textinline{CMakeCache.txt  CMakeFiles  cmake_install.cmake  Makefile}
等文件,其中生成的Makefile文件能用于编译生成程序。
然后进行工程的实际构建,在这个目录输入 make 命令,大概会得到如下的输出:
\begin{bash}
[ 50%] Building CXX object CMakeFiles/t.dir/test1.cc.o    
[100%] Linking CXX executable t                           
[100%] Built target t                                     
\end{bash}
如果你需要看到 make 构建的详细过程,可以使用 make VERBOSE=1 或者 VERBOSE=1
make 命令来进行构建。这时候,我们需要的目标文件 t 已经构建完成,位于当前目录,尝试运行一下:
\bashinline{./t},程序等待输入两个数字计算两个数字的和。\\
\textbf{简单的解释}:
我们来重新看一下 CMakeLists.txt,这个文件是 cmake 的构建定义文件,文件名
是大小写相关的,如果工程存在多个目录,需要确保每个要管理的目录都存在一个
CMakeLists.txt。(关于多目录构建,后面我们会提到,这里不作过多解释)。
上面例子中的 CMakeLists.txt 文件内容如下:
\cmakefile{cxxcode/cmake_code/ex1/CMakeLists.txt}
PROJECT 指令的语法是:

\Textinline{PROJECT(projectname [CXX] [C] [Java])}

你可以用这个指令定义工程名称,并可指定工程支持的语言,支持的语言列表是可以忽略的,
默认情况表示支持所有语言。这个指令隐式的定义了两个 cmake 变量:
\begin{itemize}
\item \Textinline{<projectname>_BINARY_DIR} 
\item \Textinline{<projectname>_SOURCE_DIR}
\end{itemize}
这里就是\Textinline{ex1_BINARY_DIR} 和 \Textinline{ex1_SOURCE_DIR}
(所以 CMakeLists.txt 中两个 MESSAGE指令可以直接使用了这两个变量),因为采用的是内部编译,两个变量目前指的都是工程所
在路径\bashinline{ /home/liushuai/linux/cxxcode/cmake_code/},后面我们会讲到外部编译,两者所指代的内容会有所不同。
同时 cmake 系统也帮助我们预定义了 \Textinline{PROJECT_BINARY_DIR} 和 \Textinline{PROJECT_SOURCE_DIR}
变量,它们的值分别跟 \Textinline{ex1_BINARY_DIR} 与 \Textinline{ex1_SOURCE_DIR} 一致。
为了统一起见,建议以后直接使用 \Textinline{PROJECT_BINARY_DIR},\Textinline{PROJECT_SOURCE_DIR},即
使修改了工程名称,也不会影响这两个变量。如果使用了
\Textinline{<projectname>_SOURCE_DIR} ,修改工程名称后,需要同时修改这些变量。
SET 指令的语法是:

\cmakeinline{SET(VAR [VALUE] [CACHE TYPE DOCSTRING [FORCE]])}\\*
现阶段,你只需要了解 SET 指令可以用来显式的定义变量即可。
比如我们用到的是:

\cmakeinline{SET(SRC_LIST test1.cc)}\\*
如果有多个源文件,也可以定义成:
\cmakeinline{SET(SRC_LIST test1.cc t1.c t2.c)}。

MESSAGE 指令的语法是:\\
\cmakeinline{MESSAGE([SEND_ERROR | STATUS | FATAL_ERROR] "message to display"...)}
这个指令用于向终端输出用户定义的信息,包含了三种类型:
\begin{itemize}
\item SEND\_ERROR,产生错误,生成过程被跳过。
\item SATUS ,输出前缀为 — 的信息。FATAL\_ERROR,立即终止所有 cmake 过程.我们在这里
使用的是 STATUS 信息输出,演示了由 PROJECT 指令定义的两个隐式变量
\Textinline{ex1_BINARY_DIR} 和\Textinline{ex1_SOURCE_DIR。}
\end{itemize}

\cmakeinline{ADD_EXECUTABLE(t ${SRC_LIST})}
定义了这个工程会生成一个文件名为 t 的可执行文件,相关的源文件是 
\Textinline{SRC_LIST} 中定义的源文件列表, 本例中你也可以直接
写成 \cmakeinline{ADD_EXECUTABLE(t test1.cc)}。
在本例我们使用了\$\{\}来引用变量,这是 cmake 的变量应用方式,但是,
有一些例外,比
如在 IF 控制语句,变量是直接使用变量名引用,而不需要\$\{\}。如果使用了\$\{\}
去应用变量,其实 IF 会去判断名为\$\{\}所代表的值的变量,
那当然是不存在的了。
将本例改写成一个最简化的 CMakeLists.txt:
\begin{cmake}
PROJECT(ex1)
ADD_EXECUTABLE(hello test1.cc)
\end{cmake}
4,基本语法规则
前面提到过,cmake 其实仍然要使用 ” cmake 语言和语法 ” 去构建,上面的内容就是所谓的
”cmake 语言和语法 ” ,最简单的语法规则是:
\begin{enumerate}
\item 变量使用\$\{\}方式取值,但是在 IF 控制语句中是直接使用变量名
\item 指令(参数 1 参数 2...)参数使用括弧括起,参数之间使用空格或分号分开。以上面的 ADD\_EXECUTABLE 指令为例,如果存在另外一个 func.cc 源文件,就要写成:\\
\cmakeinline{ADD_EXECUTABLE(t test1.cc func.cc)}或者：\\
\cmakeinline{ADD_EXECUTABLE(t test1.cc;func.cc)}
\item 指令是大小写无关的,参数和变量是大小写相关的。但推荐你全部使用大写指令。上面的 MESSAGE 指令我们已经用到了这条规则:\\
\cmakeinline{MESSAGE(STATUS “This is BINARY dir” ${ex1_BINARY_DIR})}也可以写成:\\
\Textinline{MESSAGE(STATUS “This is BINARY dir ${ex1_BINARY_DIR}”)}\\
这里需要特别解释的是作为工程名的 ex1 和生成的可执行文件 t 是没有任何关系的。
t 定义了可执行文件的文件名,你完全可以写成:\\
\cmakeinline{ADD_EXECUTABLE(t test1.cc)}编译后会生成一个 t 可执行文件。
\item 关于语法的疑惑,cmake 的语法还是比较灵活而且考虑到各种情况,比如：\\
\cmakeinline{SET(SRC_LIST test1.cc)}也可以写成:\\ 
\cmakeinline{SET(SRC_LIST “test1.cc”)}
是没有区别的,但是假设一个源文件的文件名是 fu c.cc(文件名中间包含了空格)。
这时候就必须使用双引号,如果写成了\\
\cmakeinline{SET(SRC_LIST fu nc.c)},就会出现错误,提示
你找不到 fu 文件和 nc.c 文件。这种情况,就必须写成:\\
\cmakeinline{SET(SRC_LIST “fu nc.c”)}
此外,你可以可以忽略掉 source 列表中的源文件后缀,比如可以写成：\\
\cmakeinline{ADD_EXECUTABLE(t test1)},cmake 会自动的在本目录查找 test1.cc 或者test1.cc等,当然,最好不要偷这个懒,以免这个目录确实存在一个 test1.cc 一个 test1.
同时参数也可以使用分号来进行分割。
下面的例子也是合法的:\\*
\cmakeinline{ADD_EXECUTABLE(t1 test1.cc t1.cc)}可以写成： \\
\cmakeinline{ADD_EXECUTABLE(t1 test1.cc;t1.cc)}\\
我们只需要在编写 CMakeLists.txt 时注意形成统一的风格即可。
\item 清理工程:跟经典的 autotools 系列工具一样,运行:
\bashinline{make clean}即可对构建结果进行清理。
\end{enumerate}
\subsection{外部构建}
本小节的任务是让前面的 Hello World 更像一个工程,我们需要做的是:
\begin{enumerate}
\item 为工程添加一个子目录 src,用来放置工程源代码。
\item 添加一个子目录 doc,用来放置这个工程的文档 hello.txt。
\item 在工程目录添加文本文件 COPYRIGHT, README。
\item 在工程目录添加一个 runhello.sh 脚本,用来调用 hello 二进制。
\item 将构建后的目标文件放入构建目录的 bin 子目录。
\item 最终安装这些文件:将 hello 二进制与 runhello.sh 安装至/usr/bin,
将 doc 目录的内容以及 COPYRIGHT/README 安装到/usr/share/doc/cmake/t2。
\end{enumerate}

\textbf{准备工作:}\\
在/backup/cmake/目录下建立 t2 目录。
将 t1 工程的 main.c 和 CMakeLists.txt 拷贝到 t2 目录中。

\textbf{添加子目录 src:}
\begin{bash}
mkdir src
mv main.c src
\end{bash}

现在的工程看起来是这个样子:
一个子目录 src,一个 CMakeLists.txt。
上一节我们提到,需要为任何子目录建立一个 CMakeLists.txt,
进入子目录 src,编写 CMakeLists.txt 如下:
\cmakeinline{ADD_EXECUTABLE(hello main.c)}

将 t2 工程的 CMakeLists.txt 修改为:
\begin{cmake}
PROJECT(HELLO)
ADD_SUBDIRECTORY(src bin)
\end{cmake}
然后建立 build 目录,进入 build 目录进行外部编译。
\begin{bash}
cmake
..
make
\end{bash}
构建完成后,你会发现生成的目标文件 hello 位于 build/bin 目录中。语法解释:

\Textinline{ADD_SUBDIRECTORY} 指令:\\
\Textinline{ADD_SUBDIRECTORY(source_dir [binary_dir] [EXCLUDE_FROM_ALL])}
这个指令用于向当前工程添加存放源文件的子目录,并可以指定中间二进制和目标二进制存
放的位置。EXCLUDE\_FROM\_ALL 参数的含义是将这个目录从编译过程中排除,比如,工程
的 example,可能就需要工程构建完成后,再进入 example 目录单独进行构建(当然,你
也可以通过定义依赖来解决此类问题)。

上面的例子定义了将 src 子目录加入工程,并指定编译输出(包含编译中间结果)路径为
bin 目录。如果不进行 bin 目录的指定,那么编译结果(包括中间结果)都将存放在
build/src 目录(这个目录跟原有的 src 目录对应),指定 bin 目录后,相当于在编译时
将 src 重命名为 bin,所有的中间结果和目标二进制都将存放在 bin 目录。
这里需要提一下的是 SUBDIRS 指令,使用方法是:
SUBDIRS(dir1 dir2...),但是这个指令已经不推荐使用。它可以一次添加多个子目录,
并且,即使外部编译,子目录体系仍然会被保存。
如果我们在上面的例子中将 ADD\_SUBDIRECTORY (src bin)修改为 SUBDIRS(src)。
那么在 build 目录中将出现一个 src 目录,生成的目标代码 hello 将存放在 src 目录中。

\textbf{换个地方保存目标二进制}\\
不论是 SUBDIRS 还是 ADD\_SUBDIRECTORY 指令(不论是否指定编译输出目录),我们都可
以通过 SET 指令重新定义 EXECUTABLE\_OUTPUT\_PATH 和 LIBRARY\_OUTPUT\_PATH 变量
来指定最终的目标二进制的位置(指最终生成的 hello 或者最终的共享库,不包含编译生成
的中间文件)
\begin{cmake}
SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin)
SET(LIBRARY_OUTPUT_PATH ${PROJECT_BINARY_DIR}/lib)
\end{cmake}
在第一节我们提到了<projectname>\_BINARY\_DIR 和 PROJECT\_BINARY\_DIR 变量,他
们指的编译发生的当前目录,如果是内部编译,就相当于 PROJECT\_SOURCE\_DIR 也就是
工程代码所在目录,如果是外部编译,指的是外部编译所在目录,也就是本例中的 build
目录。
所以,上面两个指令分别定义了:
可执行二进制的输出路径为 build/bin 和库的输出路径为 build/lib.
本节我们没有提到共享库和静态库的构建,所以,你可以不考虑第二条指令。
问题是,我应该把这两条指令写在工程的 CMakeLists.txt 还是 src 目录下的
CMakeLists.txt,把握一个简单的原则,在哪里 ADD\_EXECUTABLE 或 ADD\_LIBRARY,
如果需要改变目标存放路径,就在哪里加入上述的定义。在这个例子里,当然就是指 src 下的 CMakeLists.txt 了。

\textbf{如何安装}\\
安装的需要有两种,一种是从代码编译后直接 make install 安装,一种是打包时的指定
目录安装。
所以,即使最简单的手工编写的 Makefile,看起来也是这个样子的:
\begin{bash}
DESTDIR=
install:
mkdir -p $(DESTDIR)/usr/bin
install -m 755 hello $(DESTDIR)/usr/bin
\end{bash}
你可以通过:\bashinline{make install}
将 hello 直接安装到/usr/bin 目录,也可以通过 make install DESTDIR=/tmp/test 将他安装在
/tmp/test/usr/bin 目录,打包时这个方式经常被使用。
稍微复杂一点的是还需要定义 PREFIX,一般 autotools 工程,会运行这样的指令:\\
\bashinline{./configure –prefix=/usr} 或者\\
\bashinline{./configure --prefix=/usr/local} 来指定
PREFIX比如上面的 Makefile 就可以改写成:
\begin{bash}
DESTDIR=/
PREFIX=usr
install:
mkdir -p $(DESTDIR)/$(PREFIX)/bin
install -m 755 hello $(DESTDIR)/$(PREFIX)/bin
\end{bash}
那么我们的 HelloWorld 应该怎么进行安装呢?
这里需要引入一个新的 cmake 指令 INSTALL 和一个非常有用的变量
\Textinline{CMAKE_INSTALL_PREFIX}。
CMAKE\_INSTALL\_PREFIX 变量类似于 configure 脚本的 – prefix,常见的使用方法看
起来是这个样子:

\bashinline{cmake -DCMAKE_INSTALL_PREFIX=/usr .}
INSTALL 指令用于定义安装规则,安装的内容可以包括目标二进制、动态库、静态库以及
文件、目录、脚本等。INSTALL 指令包含了各种安装类型,我们需要一个个分开解释:
目标文件的安装:
\begin{cmake}
INSTALL(TARGETS targets...
[[ARCHIVE|LIBRARY|RUNTIME]
[DESTINATION <dir>]
[PERMISSIONS permissions...]
[CONFIGURATIONS
[Debug|Release|...]]
[COMPONENT <component>]
[OPTIONAL]
] [...])
\end{cmake}
参数中的 TARGETS 后面跟的就是我们通过 ADD\_EXECUTABLE 或者 ADD\_LIBRARY 定义的
目标文件,可能是可执行二进制、动态库、静态库。
目标类型也就相对应的有三种,ARCHIVE 特指静态库,LIBRARY 特指动态库,RUNTIME
特指可执行目标二进制。
DESTINATION 定义了安装的路径,如果路径以/开头,那么指的是绝对路径,这时候\\*
CMAKE\_INSTALL\_PREFIX 其实就无效了。如果你希望使用 CMAKE\_INSTALL\_PREFIX 来
定义安装路径,就要写成相对路径,即不要以/开头,那么安装后的路径就是：

\bashinline{${CMAKE_INSTALL_PREFIX}/<DESTINATION 定义的路径>}
举个简单的例子:
\begin{cmake}
INSTALL(TARGETS myrun mylib mystaticlib
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION libstatic
)
\end{cmake}
上面的例子会将:
可执行二进制 myrun 安装到：

\bashinline{${CMAKE_INSTALL_PREFIX}/bin}\\
目录动态库 libmylib 安装到\bashinline{${CMAKE_INSTALL_PREFIX}/lib} 目录
静态库 libmystaticlib 安装到
\bashinline{${CMAKE_INSTALL_PREFIX}/libstatic}\\
目录特别注意的是你不需要关心 TARGETS 具体生成的路径,只需要写上
TARGETS 名称就可以了。

普通文件的安装:
\begin{cmake}
INSTALL(FILES files... DESTINATION <dir>
[PERMISSIONS permissions...]
[CONFIGURATIONS [Debug|Release|...]]
[COMPONENT <component>]
[RENAME <name>] [OPTIONAL])
\end{cmake}
可用于安装一般文件,并可以指定访问权限,文件名是此指令所在路径下的相对路径。如果默认不定义权限 PERMISSIONS,安装后的权限为:
OWNER\_WRITE, OWNER\_READ,
GROUP\_READ,和 WORLD\_READ,即 644 权限。
非目标文件的可执行程序安装(比如脚本之类):
\begin{cmake}
INSTALL(PROGRAMS files... DESTINATION <dir>
[PERMISSIONS permissions...]
[CONFIGURATIONS [Debug|Release|...]]
[COMPONENT <component>]
[RENAME <name>] [OPTIONAL])
\end{cmake}
跟上面的 FILES 指令使用方法一样,唯一的不同是安装后权限为:
OWNER\_EXECUTE, GROUP\_EXECUTE, 和 WORLD\_EXECUTE,即 755 权限
目录的安装:
\begin{cmake}
INSTALL(DIRECTORY dirs... DESTINATION <dir>
[FILE_PERMISSIONS permissions...]
[DIRECTORY_PERMISSIONS permissions...]
[USE_SOURCE_PERMISSIONS]
[CONFIGURATIONS [Debug|Release|...]]
[COMPONENT <component>]
[[PATTERN <pattern> | REGEX <regex>]
[EXCLUDE] [PERMISSIONS permissions...]] [...])
\end{cmake}
这里主要介绍其中的 DIRECTORY、PATTERN 以及 PERMISSIONS 参数。
DIRECTORY 后面连接的是所在 Source 目录的相对路径,但务必注意:
abc 和 abc/有很大的区别。
如果目录名不以/结尾,那么这个目录将被安装为目标路径下的 abc,如果目录名以/结尾,
代表将这个目录中的内容安装到目标路径,但不包括这个目录本身。
PATTERN 用于使用正则表达式进行过滤,PERMISSIONS 用于指定 PATTERN 过滤后的文件
权限。
我们来看一个例子:
\begin{cmake}
INSTALL(DIRECTORY icons scripts/ DESTINATION share/myprojPATTERN "CVS" EXCLUDE
PATTERN "scripts/*"
PERMISSIONS OWNER_EXECUTE OWNER_WRITE OWNER_READ
GROUP_EXECUTE GROUP_READ)
\end{cmake}
这条指令的执行结果是:
将 icons 目录安装到 <prefix>/share/myproj,将 scripts/中的内容安装到
<prefix>/share/myproj
不包含目录名为 CVS 的目录,对于 scripts/* 文件指定权限为 OWNER\_EXECUTE
OWNER\_WRITE OWNER\_READ GROUP\_EXECUTE GROUP\_READ.
安装时 CMAKE 脚本的执行:
\cmakeinline{INSTALL([[SCRIPT <file>] [CODE <code>]] [...])}
\begin{itemize}
\item SCRIPT 参数用于在安装时调用 cmake 脚本文件(也就是<abc>.cmake 文件)
\item CODE 参数用于执行 CMAKE 指令,必须以双引号括起来。比如:
\end{itemize}
\cmakeinline{INSTALL(CODE "MESSAGE(\"Sample install message.\")")}
安装还有几个被标记为过时的指令,比如 INSTALL\_FILES 等,这些指令已经不再推荐使
用,所以,这里就不再赘述了。
下面,我们就来改写我们的工程文件,让他来支持各种文件的安装,并且,我们要使用
CMAKE\_INSTALL\_PREFIX 指令。

5,修改 Helloworld 支持安装
在本节开头我们定义了本节的任务如下:
\begin{enumerate}
\item 为工程添加一个子目录 src,用来存储源代码;
\item 添加一个子目录 doc,用来存储这个工程的文档 hello.txt
\item 在工程目录添加文本文件 COPYRIGHT, README;
\item 在工程目录添加一个 runhello.sh 脚本,用来调用 hello 二进制
\item 将构建后的目标文件放入构建目录的 bin 子目录;
\item 最终安装这些文件:将 hello 二进制与 runhello.sh 安装至/<prefix>/bin,将
doc 目录中的 hello.txt 以及 COPYRIGHT/README 安装到
/<prefix>/share/doc/cmake/t2,将
首先我们先补上为添加的文件。
添加 doc 目录及文件:cd /backup/cmake/t2
\begin{bash}
mkdir doc
vi doc/hello.txt
\end{bash}
随便填写一些内容并保存
在工程目录添加 runhello.sh 脚本,内容为:\Textinline{hello}
添加工程目录中的 COPYRIGHT 和 README
\begin{bash}
touch COPYRIGHT
touch README
\end{bash}
\end{enumerate}
下面改写各目录的 CMakeLists.txt 文件。
\begin{enumerate}
\item 安装 COPYRIGHT/README,直接修改主工程文件 CMakelists.txt,加入以下指令:
cmakeinline{STALL(FILES COPYRIGHT README DESTINATION share/doc/cmake/t2)}
\item 安装 runhello.sh,直接修改主工程文件 CMakeLists.txt,加入如下指令:

\cmakeinline{INSTALL(PROGRAMS runhello.sh DESTINATION bin)}\\*
\item 安装 doc 中的 hello.txt,这里有两种方式:一是通过在 doc 目录建立
CMakeLists.txt 并将 doc 目录通过 ADD\_SUBDIRECTORY 加入工程来完成。另一种方法
是直接在工程目录通过
INSTALL(DIRECTORY 来完成),前者比较简单,各位可以根据兴趣自己完成,我们来尝试
后者,顺便演示以下 DIRECTORY 的安装。
因为 hello.txt 要安装到/<prefix>/share/doc/cmake/t2,所以我们不能直接安装
整个 doc 目录,这里采用的方式是安装 doc 目录中的内容,也就是使用 ” doc/”
在工程文件中添加
INSTALL(DIRECTORY doc/ DESTINATION share/doc/cmake/t2)
\end{enumerate}
6,尝试我们修改的结果:
现在进入 build 目录进行外部编译,注意使用 CMAKE\_INSTALL\_PREFIX 参数,这里我们
将它安装到了/tmp/t2 目录:

\bashinline{cmake -DCMAKE_INSTALL_PREFIX=/tmp/t2/usr ..}
然后运行
\begin{bash}
make
make install
\end{bash}
让我们进入/tmp/t2 目录看一下安装结果:
\begin{bash}
./usr
./usr/share
./usr/share/doc./usr/share/doc/cmake
./usr/share/doc/cmake/t2
./usr/share/doc/cmake/t2/hello.txt
./usr/share/doc/cmake/t2/README
./usr/share/doc/cmake/t2/COPYRIGHT
./usr/bin
./usr/bin/hello
./usr/bin/runhello.sh
\end{bash}
如果你要直接安装到系统,可以使用如下指令:
\bashinline{cmake -DCMAKE_INSTALL_PREFIX=/usr ..}
7,一个疑问

如果我没有定义CMAKE\_INSTALL\_PREFIX 会安装到什么地方?
你可以尝试一下：
\begin{bash}
cmake ..
make
make install,
\end{bash}
你会发现
CMAKE\_INSTALL\_PREFIX 的默认定义是/usr/local

8,小结:

本小节主要描述了如何在工程中使用多目录、各种安装指令以及
CMAKE\_INSTALL\_PREFIX 变量(你真够牛的,这么点东西居然罗唆了这么多文字)
在下一小节,我们将探讨如何在 cmake 中构建动态库和静态库,以及如何使用外部头文件
和外部共享库,毕竟,这是程序编写中最长使用的(对了,你知道用怎样的 gcc 参数可以
直接构建静态库和动态库吗?)
\subsection{静态库和动态库}
\textbf{本节的任务:}

1,建立一个静态库和动态库,提供 HelloFunc 函数供其它程序编程使用,HelloFunc
向终端输出 Hello World 字符串。

2,安装头文件与共享库。

一,准备工作:
在/backup/cmake 目录建立 t3 目录,用于存放本节涉及到的工程
二,建立共享库
cd /backup/cmake/t3
mkdir lib
在 t3 目录下建立 CMakeLists.txt,内容如下:
\begin{cmake}
PROJECT(HELLOLIB)
ADD_SUBDIRECTORY(lib)
\end{cmake}
在 lib 目录下建立两个源文件 hello.c 与 hello.h
hello.c 内容如下:
\begin{cpp}
#include “hello.h”
void HelloFunc()
{
    printf("Hello World\n");
}
\end{cpp}
hello.h 内容如下:
\begin{cpp}
#ifndef HELLO_H
#define HELLO_H
#include <stdio.h>
void HelloFunc();
#endif
\end{cpp}
在 lib 目录下建立 CMakeLists.txt,内容如下:

\Textinline{SET(LIBHELLO_SRC hello.c)}

\Textinline{ADD_LIBRARY(hello SHARED ${LIBHELLO_SRC})}

三,编译共享库:
仍然采用 out-of-source 编译的方式,按照习惯,我们建立一个 build 目录,在 build
目录中
\begin{bash}
cmake ..
make
\end{bash}
这时,你就可以在 lib 目录得到一个 libhello.so,这就是我们期望的共享库。
如果你要指定 libhello.so 生成的位置,可以通过在主工程文件 CMakeLists.txt 中修
改 ADD\_SUBDIRECTORY(lib)指令来指定一个编译输出位置或者
在 lib/CMakeLists.txt 中添加
SET(LIBRARY\_OUTPUT\_PATH <路径>)来指定一个新的位置。
这两者的区别我们上一节已经提到了,所以,这里不再赘述,下面,我们解释一下一个新的
指令 ADD\_LIBRARY
\begin{cmake}
ADD_LIBRARY(libname
[SHARED|STATIC|MODULE]
[EXCLUDE_FROM_ALL]
source1 source2 ... sourceN)
\end{cmake}
你不需要写全 libhello.so,只需要填写 hello 即可,cmake 系统会自动为你生成
libhello.X
类型有三种:
\begin{itemize}
\item SHARED,动态库
\item STATIC,静态库
\item MODULE,在使用 
dyld 的系统有效,如果不支持 dyld,则被当作 SHARED 对待。
\end{itemize}
EXCLUDE\_FROM\_ALL 参数的意思是这个库不会被默认构建,除非有其它的组件依赖或者手
工构建。

四,添加静态库:
同样使用上面的指令,我们在支持动态库的基础上再为工程添加一个静态库,按照一般的习
惯,静态库名字跟动态库名字应该是一致的,只不过后缀是.a 罢了。
下面我们用这个指令再来添加静态库:
ADD\_LIBRARY(hello STATIC \${LIBHELLO\_SRC})然后再在 build 目录进行外部编译,我们会发现,
静态库根本没有被构建,仍然只生成了
一个动态库。因为 hello 作为一个 target 是不能重名的,所以,静态库构建指令无效。
如果我们把上面的 hello 修改为 hello\_static:
\cmakeinline{ADD_LIBRARY(hello_static STATIC ${LIBHELLO_SRC})}
就可以构建一个 libhello\_static.a 的静态库了。
这种结果显示不是我们想要的,我们需要的是名字相同的静态库和动态库,因为 target 名
称是唯一的,所以,我们肯定不能通过 ADD\_LIBRARY 指令来实现了。这时候我们需要用到
另外一个指令:
SET\_TARGET\_PROPERTIES,其基本语法是:
\begin{cmake}
SET_TARGET_PROPERTIES(target1 target2 ...
PROPERTIES prop1 value1
prop2 value2 ...)
\end{cmake}
这条指令可以用来设置输出的名称,对于动态库,还可以用来指定动态库版本和 API 版本。
在本例中,我们需要做的是向 lib/CMakeLists.txt 中添加一条:
SET\_TARGET\_PROPERTIES(hello\_static PROPERTIES OUTPUT\_NAME "hello")
这样,我们就可以同时得到 libhello.so/libhello.a 两个库了。
与它对应的指令是:
\Textinline{GET_TARGET_PROPERTY(VAR target property)}
具体用法如下例,我们向 lib/CMakeListst.txt 中添加:
\begin{cmake}
GET_TARGET_PROPERTY(OUTPUT_VALUE hello_static OUTPUT_NAME)
MESSAGE(STATUS “This is the hello_static 
OUTPUT_NAME:”${OUTPUT_VALUE})
\end{cmake}
如果没有这个属性定义,则返回 NOTFOUND.
让我们来检查一下最终的构建结果,我们发现,libhello.a 已经构建完成,位于
build/lib 目录中,但是 libhello.so 去消失了。这个问题的原因是:cmake 在构建一个新的 target 时,会尝试清理掉其它使用这个名字的库,因为,在构建 libhello.a 时,
就会清理掉 libhello.so.
为了回避这个问题,比如再次使用 SET\_TARGET\_PROPERTIES 定义
CLEAN\_DIRECT\_OUTPUT 属性。
向 lib/CMakeLists.txt 中添加:

SET\_TARGET\_PROPERTIES(hello PROPERTIES CLEAN\_DIRECT\_OUTPUT 1)

SET\_TARGET\_PROPERTIES(hello\_static PROPERTIES CLEAN\_DIRECT\_OUTPUT  
1)
这时候,我们再次进行构建,会发现 build/lib 目录中同时生成了 libhello.so 和
libhello.a
五,动态库版本号
按照规则,动态库是应该包含一个版本号的,我们可以看一下系统的动态库,一般情况是
\begin{itemize}
\item libhello.so.1.2
\item libhello.so ->libhello.so.1
\item libhello.so.1->libhello.so.1.2
\end{itemize}
为了实现动态库版本号,我们仍然需要使用 SET\_TARGET\_PROPERTIES 指令。
具体使用方法如下:
SET\_TARGET\_PROPERTIES(hello PROPERTIES VERSION 1.2 SOVERSION 1)
VERSION 指代动态库版本,SOVERSION 指代 API 版本。
将上述指令加入 lib/CMakeLists.txt 中,重新构建看看结果。
在 build/lib 目录会生成:
\begin{itemize}
\item libhello.so.1.2
\item libhello.so.1->libhello.so.1.2
\item libhello.so ->libhello.so.1
\end{itemize}
六,安装共享库和头文件
以上面的例子,我们需要将 libhello.a, libhello.so.x 以及 hello.h 安装到系统目
录,才能真正让其它人开发使用,在本例中我们将 hello 的共享库安装到<prefix>/lib
目录,将 hello.h 安装到<prefix>/include/hello 目录。
利用上一节了解到的 INSTALL 指令,我们向 lib/CMakeLists.txt 中添加如下指令:
INSTALL(TARGETS hello hello\_staticLIBRARY DESTINATION lib
ARCHIVE DESTINATION lib)
INSTALL(FILES hello.h DESTINATION include/hello)
注意,静态库要使用 ARCHIVE 关键字
通过:
\begin{bash}
cmake -DCMAKE_INSTALL_PREFIX=/usr ..
make 
make install
\end{bash} 

我们就可以将头文件和共享库安装到系统目录/usr/lib 和/usr/include/hello 中了。
七,小结:
本小节,我们谈到了:
如何通过 ADD\_LIBRARY 指令构建动态库和静态库。
如何通过 SET\_TARGET\_PROPERTIES 同时构建同名的动态库和静态库。
如何通过 SET\_TARGET\_PROPERTIES 控制动态库版本
最终使用上一节谈到的 INSTALL 指令来安装头文件和动态、静态库。
在下一节,我们需要编写另一个高级一点的 Hello World 来演示怎么使用我们已经构建的
构建的共享库 libhello 和外部头文件。
\subsection{使用外部共享库和头文件}
抱歉,本节仍然继续折腾 Hello World.
上一节我们已经完成了 libhello 动态库的构建以及安装,本节我们的任务很简单:
编写一个程序使用我们上一节构建的共享库。
1,准备工作:
请在/backup/cmake 目录建立 t4 目录,本节所有资源将存储在 t4 目录。
2,重复以前的步骤,建立 src 目录,编写源文件 main.c,内容如下:
\begin{cpp}
#include <hello.h>
int main()
{
    HelloFunc();
return 0;
}
\end{cpp}
编写工程主文件 CMakeLists.txt
\begin{cmake}
PROJECT(NEWHELLO)
ADD_SUBDIRECTORY(src)
\end{cmake}

编写 src/CMakeLists.txt 
ADD\_EXECUTABLE(main main.c)
上述工作已经严格按照我们前面季节提到的内容完成了。

3,外部构建
按照习惯,仍然建立 build 目录,使用 cmake ..方式构建。
过程:
\begin{bash}
cmake ..
make
\end{bash}
构建失败,如果需要查看细节,可以使用第一节提到的方法
make VERBOSE=1 来构建
错误输出为是:
 /backup/cmake/t4/src/main.c:1:19: error: hello.h: 没有那个文件或目录
 4,引入头文件搜索路径。
 hello.h 位于/usr/include/hello 目录中,并没有位于系统标准的头文件路径,
 (有人会说了,白痴啊,你就不会 include <hello/hello.h>,同志,要这么干,我这
 一节就没什么可写了,只能选择一个 glib 或者 libX11 来写了,这些代码写出来很多同志
 是看不懂的)
 为了让我们的工程能够找到 hello.h 头文件,我们需要引入一个新的指令
 INCLUDE\_DIRECTORIES,其完整语法为:
 INCLUDE\_DIRECTORIES([AFTER|BEFORE] [SYSTEM] dir1 dir2 ...)
 这条指令可以用来向工程添加多个特定的头文件搜索路径,路径之间用空格分割,如果路径
 中包含了空格,可以使用双引号将它括起来,默认的行为是追加到当前的头文件搜索路径的
 后面,你可以通过两种方式来进行控制搜索路径添加的方式:
 \begin{enumerate}
\item CMAKE\_INCLUDE\_DIRECTORIES\_BEFORE,通过 SET 这个 cmake 变量为 on,可以
 将添加的头文件搜索路径放在已有路径的前面。
\item 通过 AFTER 或者 BEFORE 参数,也可以控制是追加还是置前。
 现在我们在 src/CMakeLists.txt 中添加一个头文件搜索路径,方式很简单,加入:
 INCLUDE\_DIRECTORIES(/usr/include/hello)
 进入 build 目录,重新进行构建,这是找不到 hello.h 的错误已经消失,但是出现了一个
 新的错误:\\
\bashinline{main.c:(.text+0x12): undefined reference to 'HelloFunc'}\\
 因为我们并没有 link 到共享库 libhello 上。
\end{enumerate}
 5,为 target 添加共享库
我们现在需要完成的任务是将目标文件链接到 libhello,这里我们需要引入两个新的指令
LINK\_DIRECTORIES 和 TARGET\_LINK\_LIBRARIES，
LINK\_DIRECTORIES 的全部语法是:

\Textinline{LINK_DIRECTORIES(directory1 directory2 ...)}
这个指令非常简单,添加非标准的共享库搜索路径,比如,在工程内部同时存在共享库和可
执行二进制,在编译时就需要指定一下这些共享库的路径。这个例子中我们没有用到这个指
令。
TARGET\_LINK\_LIBRARIES 的全部语法是:
\begin{cmake}
TARGET_LINK_LIBRARIES(target library1
<debug | optimized> library2
...)
\end{cmake}
这个指令可以用来为 target 添加需要链接的共享库,本例中是一个可执行文件,但是同样可以用于为自己编写的共享库添加共享库链接。
为了解决我们前面遇到的 HelloFunc 未定义错误,我们需要做的是向
src/CMakeLists.txt 中添加如下指令:
TARGET\_LINK\_LIBRARIES(main hello)
也可以写成
TARGET\_LINK\_LIBRARIES(main libhello.so)
这里的 hello 指的是我们上一节构建的共享库 libhello.
进入 build 目录重新进行构建。
\begin{bash}
cmake ..
make
\end{bash}
这是我们就得到了一个连接到 libhello 的可执行程序 main,位于 build/src 目录,运
行 main 的结果是输出:
Hello World 
让我们来检查一下 main 的链接情况:
\begin{bash}
ldd src/main
linux-gate.so.1 =>
(0xb7ee7000)
libhello.so.1 => /usr/lib/libhello.so.1 (0xb7ece000)
libc.so.6 => /lib/libc.so.6 (0xb7d77000)
/lib/ld-linux.so.2 (0xb7ee8000)
\end{bash}
可以清楚的看到 main 确实链接了共享库 libhello,而且链接的是动态库
libhello.so.1
那如何链接到静态库呢?
方法很简单:
将 TARGET\_LINK\_LIBRRARIES 指令修改为:
TARGET\_LINK\_LIBRARIES(main libhello.a)
重新构建后再来看一下 main 的链接情况
\begin{bash}
ldd src/main
linux-gate.so.1 =>
(0xb7fa8000)
libc.so.6 => /lib/libc.so.6 (0xb7e3a000)
/lib/ld-linux.so.2 (0xb7fa9000)
\end{bash}
说明,main 确实链接到了静态库 libhello.a

6,特殊的环境变量 CMAKE\_INCLUDE\_PATH 和 CMAKE\_LIBRARY\_PATH
务必注意,这两个是环境变量而不是 cmake 变量。
使用方法是要在 bash 中用 export 或者在 csh 中使用 set 命令设置或者
CMAKE\_INCLUDE\_PATH=/home/include cmake ..等方式。
这两个变量主要是用来解决以前 autotools 工程中
--extra-include-dir 等参数的支持的。
也就是,如果头文件没有存放在常规路径(/usr/include, /usr/local/include 等),
则可以通过这些变量就行弥补。
我们以本例中的 hello.h 为例,它存放在/usr/include/hello 目录,所以直接查找肯
定是找不到的。
前面我们直接使用了绝对路径 INCLUDE\_DIRECTORIES(/usr/include/hello)告诉工
程这个头文件目录。
为了将程序更智能一点,我们可以使用 CMAKE\_INCLUDE\_PATH 来进行,使用 bash 的方法
如下:
\bashinline{export CMAKE_INCLUDE_PATH=/usr/include/hello}
然后在头文件中将 INCLUDE\_DIRECTORIES(/usr/include/hello)替换为:
\begin{cmake}
FIND_PATH(myHeader hello.h)
IF(myHeader)
INCLUDE_DIRECTORIES(${myHeader})
ENDIF(myHeader)
\end{cmake}
上述的一些指令我们在后面会介绍。
这里简单说明一下,FIND\_PATH 用来在指定路径中搜索文件名,比如:\\
\Textinline{FIND_PATH(myHeader NAMES hello.h PATHS /usr/include }
/usr/include/hello)
这里我们没有指定路径,但是,cmake 仍然可以帮我们找到 hello.h 存放的路径,就是因
为我们设置了环境变量 CMAKE\_INCLUDE\_PATH。
如果你不使用 FIND\_PATH,CMAKE\_INCLUDE\_PATH 变量的设置是没有作用的,你不能指
望它会直接为编译器命令添加参数
\Textinline{-I<CMAKE\_INCLUDE\_PATH>}。
以此为例,CMAKE\_LIBRARY\_PATH 可以用在 FIND\_LIBRARY 中。
同样,因为这些变量直接为 FIND\_指令所使用,所以所有使用 FIND\_指令的 cmake 模块都
会受益。

7,小节:

本节我们探讨了:
如何通过 INCLUDE\_DIRECTORIES 指令加入非标准的头文件搜索路径。
如何通过 LINK\_DIRECTORIES 指令加入非标准的库文件搜索路径。
如果通过 TARGET\_LINK\_LIBRARIES 为库或可执行二进制加入库链接。
并解释了如果链接到静态库。
到这里为止,您应该基本可以使用 cmake 工作了,但是还有很多高级的话题没有探讨,比
如编译条件检查、编译器定义、平台判断、如何跟 pkgconfig 配合使用等等。
到这里,或许你可以理解前面讲到的 “ cmake 的使用过程其实就是学习 cmake 语言并编写
cmake 程序的过程 ” ,既然是 “ cmake 语言 ” ,自然涉及到变量、语法等.
\bashinline{link_directories(dir1 dir2,....)}相当于clang++中的 -L 主要用于将指定的动态链接库路径添加到工程搜索路径中。
\bashinline{link_libraries(dir1 dir2,...)}:如果path是目录，cmake会自动搜索目录中的库文件，如果是库文件的完整路径，则直接将需要的库添加到工程中，目录或者文件可以有多个。
\bashinline{target_link_libraries(pro libname)}:pro表示当前工程名称，libname是添加动态库的名称，如果你想添加add库，他的静态库名称通常是libadd.a,动态库名称为libadd.so,这里你可以指定两者任一或者直接指定add。

\bashinline{FIND_LIBRARY(MY_LIB libmylib.a ./)}在当前路径\bashinline{./}搜索静态链接库libmylib.a保存在变量\bashinline{MY_LIB},使用这个库可以通过:\bashinline{TARGET_LINK_LIBRARIES(${PROJECT_NAME} ${MY_LIB})}。下一节,我们将抛开程序的话题,看看常用的 CMAKE 变量以及一些基本的控制语法规则。`
\subsection{cmake常用变量和环境变量}
\textbf{一,cmake 变量引用的方式:}
前面我们已经提到了,使用\$\{\}进行变量的引用。在 IF 等语句中,是直接使用变量名而不
通过\$\{\}取值

\textbf{二,cmake 自定义变量的方式}：\\
主要有隐式定义和显式定义两种,前面举了一个隐式定义的例子,就是 PROJECT 指令,它
会隐式的定义<projectname>\_BINARY\_DIR 和<projectname>\_SOURCE\_DIR 两个变
量。
显式定义的例子我们前面也提到了,使用 SET 指令,就可以构建一个自定义变量了。
比如:
SET(HELLO\_SRC main.SOURCE\_PATHc),就 PROJECT\_BINARY\_DIR 可以通过
\$\{HELLO\_SRC\}来引用这个自定义变量了.

\textbf{三,cmake 常用变量：}\\
\begin{enumerate}
\item CMAKE\_BINARY\_DIR，PROJECT\_BINARY\_DIR，<projectname>\_BINARY\_DIR
这三个变量指代的内容是一致的,如果是 in source 编译,指得就是工程顶层目录,如果
是 out-of-source 编译,指的是工程编译发生的目录。PROJECT\_BINARY\_DIR 跟其它
指令稍有区别,现在,你可以理解为它们是一致的。
\item CMAKE\_SOURCE\_DIR PROJECT\_SOURCE\_DIR <projectname>\_SOURCE\_DIR
这三个变量指代的内容是一致的,不论采用何种编译方式,都是工程顶层目录。
也就是在 in source 编译时,它跟 CMAKE\_BINARY\_DIR 等变量一致。
PROJECT\_SOURCE\_DIR 跟其它指令稍有区别,现在,你可以理解为它们是一致的。
\item CMAKE\_CURRENT\_SOURCE\_DIR
指的是当前处理的 CMakeLists.txt 所在的路径,比如上面我们提到的 src 子目录。
\item CMAKE\_CURRRENT\_BINARY\_DIR 如果是 in-source 编译,它跟CMAKE\_CURRENT\_SOURCE\_DIR 
一致,如果是 out-of-source 编译,它指的是 target 编译目录。
使用我们上面提到的 ADD\_SUBDIRECTORY(src bin)可以更改这个变量的值。
使用 SET(EXECUTABLE\_OUTPUT\_PATH <新路径>)并不会对这个变量造成影响,它仅仅
修改了最终目标文件存放的路径。
\item CMAKE\_CURRENT\_LIST\_FILE
输出调用这个变量的 CMakeLists.txt 的完整路径
\item CMAKE\_CURRENT\_LIST\_LINE 
输出这个变量所在的行
\item CMAKE\_MODULE\_PATH
这个变量用来定义自己的 cmake 模块所在的路径。如果你的工程比较复杂,有可能会自己
编写一些 cmake 模块,这些 cmake 模块是随你的工程发布的,为了让 cmake 在处理
CMakeLists.txt 时找到这些模块,你需要通过 SET 指令,将自己的 cmake 模块路径设
置一下。
比如
\Textinline{SET(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)}
这时候你就可以通过 INCLUDE 指令来调用自己的模块了。
\item EXECUTABLE\_OUTPUT\_PATH 和 LIBRARY\_OUTPUT\_PATH
分别用来重新定义最终结果的存放目录,前面我们已经提到了这两个变量。
\item PROJECT\_NAME
返回通过 PROJECT 指令定义的项目名称。

\textbf{四,cmake 调用环境变量的方式}

使用\$ENV\{NAME\}指令就可以调用系统的环境变量了。
比如

\Textinline{MESSAGE(STATUS “HOME dir: $ENV{HOME}”)}
\end{enumerate}
设置环境变量的方式是:
SET(ENV{变量名} 值)
\begin{enumerate}
\item CMAKE\_INCLUDE\_CURRENTi\_DIR
自动添加 CMAKE\_CURRENT\_BINARY\_DIR 和 CMAKE\_CURRENT\_SOURCE\_DIR 到当前处理
的 CMakeLists.txt。相当于在每个 CMakeLists.txt 加入:

\cmakeinline{INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR})}
\item CMAKE\_INCLUDE\_DIRECTORIES\_PROJECT\_BEFORE
将工程提供的头文件目录始终至于系统头文件目录的前面,当你定义的头文件确实跟系统发生冲突时可以提供一些帮助。
\item CMAKE\_INCLUDE\_PATH 和 CMAKE\_LIBRARY\_PATH 我们在上一节已经提及。
\end{enumerate}
五,系统信息
\begin{enumerate}
\item CMAKE\_MAJOR\_VERSION,CMAKE 主版本号,比如 2.4.6 中的 2
\item CMAKE\_MINOR\_VERSION,CMAKE 次版本号,比如 2.4.6 中的 4
\item CMAKE\_PATCH\_VERSION,CMAKE 补丁等级,比如 2.4.6 中的 6
\item CMAKE\_SYSTEM,系统名称,比如 Linux-2.6.22
\item CMAKE\_SYSTEM\_NAME,不包含版本的系统名,比如 Linux
\item CMAKE\_SYSTEM\_VERSION,系统版本,比如 2.6.22
\item CMAKE\_SYSTEM\_PROCESSOR,处理器名称,比如 i686.
\item UNIX,在所有的类 UNIX 平台为 TRUE,包括 OS X 和 cygwin
\item WIN32,在所有的 win32 平台为 TRUE,包括 cygwin
\end{enumerate}
六,主要的开关选项:
1,CMAKE\_ALLOW\_LOOSE\_LOOP\_CONSTRUCTS,用来控制 IF ELSE 语句的书写方式,在
下一节语法部分会讲到。
2,BUILD\_SHARED\_LIBS
这个开关用来控制默认的库编译方式,如果不进行设置,使用 ADD\_LIBRARY 并没有指定库
类型的情况下,默认编译生成的库都是静态库。
如果 SET(BUILD\_SHARED\_LIBS ON)后,默认生成的为动态库。
3,CMAKE\_C\_FLAGS
设置 C 编译选项,也可以通过指令 ADD\_DEFINITIONS()添加。
4,CMAKE\_CXX\_FLAGS
设置 C++编译选项,也可以通过指令 ADD\_DEFINITIONS()添加。
小结:
本章介绍了一些较常用的 cmake 变量,这些变量仅仅是所有 cmake 变量的很少一部分,目
前 cmake 的英文文档也是比较缺乏的,如果需要了解更多的 cmake 变量,更好的方式是阅
读一些成功项目的 cmake 工程文件,比如 KDE4 的代码。

前面我们讲到了 cmake 常用的变量,相信 “ cmake 即编程 ” 的感觉会越来越明显,无论如何,
我们仍然可以看到 cmake 比 autotools 要简单很多。接下来我们就要集中的看一看
cmake 所提供的常用指令。在前面的章节我们已经讨论了很多指令的用法,如
\begin{itemize}
\item \Textinline{PROJECT}
\item \Textinline{ADD_EXECUTABLE}
\item \Textinline{INSTALL}
\item \Textinline{ADD_SUBDIRECTORY}
\item \Textinline{SUBDIRS}
\item \Textinline{INCLUDE_DIRECTORIES}
\item \Textinline{LINK_DIRECTORIES}
\item \Textinline{TARGET_LINK_LIBRARIES}
\item \Textinline{SET}
\end{itemize}
本节会引入更多的 cmake 指令,为了编写的方便,我们将按照 cmake man page 的顺序
来介绍各种指令,不再推荐使用的指令将不再介绍,INSTALL 系列指令在安装部分已经做
了非常详细的说明,本节也不在提及。(你可以将本章理解成选择性翻译,但是会加入更多
的个人理解)
\subsection{一,基本指令}
\begin{enumerate}
\item ADD\_DEFINITIONS
向 C/C++编译器添加-D 定义,比如:
ADD\_DEFINITIONS(-DENABLE\_DEBUG
-DABC),参数之间用空格分割。
如果你的代码中定义了\cppinline{#ifdef ENABLE_DEBUG #endif},这个代码块就会生效。
如果要添加其它的编译器开关,可以通过 CMAKE\_C\_FLAGS 变量和 CMAKE\_CXX\_FLAGS 变
量设置。
\item ADD\_DEPENDENCIES
定义 target 依赖的其它 target,确保在编译本 target 之前,其它的 target 已经被构
建。
ADD\_DEPENDENCIES(target-name depend-target1
depend-target2 ...)
\item ADD\_EXECUTABLE、ADD\_LIBRARY、ADD\_SUBDIRECTORY 前面已经介绍过了,这里
不再罗唆。
\item ADD\_TEST 与 ENABLE\_TESTING 指令。
ENABLE\_TESTING 指令用来控制 Makefile 是否构建 test 目标,涉及工程所有目录。语
法很简单,没有任何参数,ENABLE\_TESTING(),一般情况这个指令放在工程的主
CMakeLists.txt 中.
ADD\_TEST 指令的语法是:
ADD\_TEST(testname Exename arg1 arg2 ...)
testname 是自定义的 test 名称,Exename 可以是构建的目标文件也可以是外部脚本等
等。后面连接传递给可执行文件的参数。如果没有在同一个 CMakeLists.txt 中打开
ENABLE\_TESTING()指令,任何 ADD\_TEST 都是无效的。比如我们前面的 Helloworld 例子,可以在工程主 CMakeLists.txt 中添加
ADD\_TEST(mytest \${PROJECT\_BINARY\_DIR}/bin/main)
ENABLE\_TESTING()
生成 Makefile 后,就可以运行 make test 来执行测试了。
\item AUX\_SOURCE\_DIRECTORY
基本语法是:
AUX\_SOURCE\_DIRECTORY(dir VARIABLE)
作用是发现一个目录下所有的源代码文件并将列表存储在一个变量中,这个指令临时被用来
自动构建源文件列表。因为目前 cmake 还不能自动发现新添加的源文件。
比如
AUX\_SOURCE\_DIRECTORY(. SRC\_LIST)
ADD\_EXECUTABLE(main \${SRC\_LIST})
你也可以通过后面提到的 FOREACH 指令来处理这个 LIST
\item CMAKE\_MINIMUM\_REQUIRED
其语法为 CMAKE\_MINIMUM\_REQUIRED(VERSION versionNumber [FATAL\_ERROR])
比如 CMAKE\_MINIMUM\_REQUIRED(VERSION 2.5 FATAL\_ERROR)
如果 cmake 版本小与 2.5,则出现严重错误,整个过程中止。
\item EXEC\_PROGRAM
在 CMakeLists.txt 处理过程中执行命令,并不会在生成的 Makefile 中执行。具体语法
为:
EXEC\_PROGRAM(Executable [directory in which to run]
[ARGS <arguments to executable>]
[OUTPUT\_VARIABLE <var>]
[RETURN\_VALUE <var>])
用于在指定的目录运行某个程序,通过 ARGS 添加参数,如果要获取输出和返回值,可通过
OUTPUT\_VARIABLE 和 RETURN\_VALUE 分别定义两个变量.
这个指令可以帮助你在 CMakeLists.txt 处理过程中支持任何命令,比如根据系统情况去
修改代码文件等等。
举个简单的例子,我们要在 src 目录执行 ls 命令,并把结果和返回值存下来。可以直接在 src/CMakeLists.txt 中添加:
EXEC\_PROGRAM(ls ARGS "*.c" OUTPUT\_VARIABLE LS\_OUTPUT RETURN\_VALUE
LS\_RVALUE)
IF(not LS\_RVALUE)
MESSAGE(STATUS "ls result: " \${LS\_OUTPUT})
ENDIF(not LS\_RVALUE)
在 cmake 生成 Makefile 的过程中,就会执行 ls 命令,如果返回 0,则说明成功执行,
那么就输出 ls *.c 的结果。关于 IF 语句,后面的控制指令会提到。
\item FILE 指令
文件操作指令,基本语法为:
\begin{cmake}
FILE(WRITE filename "message to write"... )
FILE(APPEND filename "message to write"... )
FILE(READ filename variable)
FILE(GLOB
expressions]...)
variable [RELATIVE path] [globbing
FILE(GLOB_RECURSE variable [RELATIVE path]
[globbing expressions]...)
FILE(REMOVE [directory]...)
FILE(REMOVE_RECURSE [directory]...)
FILE(MAKE_DIRECTORY [directory]...)
FILE(RELATIVE_PATH variable directory file)
FILE(TO_CMAKE_PATH path result)
FILE(TO_NATIVE_PATH path result)
\end{cmake}
这里的语法都比较简单,不在展开介绍了。
9,INCLUDE 指令,用来载入 CMakeLists.txt 文件,也用于载入预定义的 cmake 模块.
INCLUDE(file1 [OPTIONAL])
INCLUDE(module [OPTIONAL])
OPTIONAL 参数的作用是文件不存在也不会产生错误。
你可以指定载入一个文件,如果定义的是一个模块,那么将在 CMAKE\_MODULE\_PATH 中搜
索这个模块并载入。
载入的内容将在处理到 INCLUDE 语句是直接执行。二,INSTALL 指令
INSTALL 系列指令已经在前面的章节有非常详细的说明,这里不在赘述,可参考前面的安
装部分。
\end{enumerate}
三,FIND\_指令
FIND\_系列指令主要包含一下指令:
FIND\_FILE(<VAR> name1 path1 path2 ...)
VAR 变量代表找到的文件全路径,包含文件名
FIND\_LIBRARY(<VAR> name1 path1 path2 ...)
VAR 变量表示找到的库全路径,包含库文件名
FIND\_PATH(<VAR> name1 path1 path2 ...)
VAR 变量代表包含这个文件的路径。
FIND\_PROGRAM(<VAR> name1 path1 path2 ...)
VAR 变量代表包含这个程序的全路径。
FIND\_PACKAGE(<name> [major.minor] [QUIET] [NO\_MODULE]
[[REQUIRED|COMPONENTS] [componets...]])
用来调用预定义在

\Textinline{CMAKE_MODULE_PATH} 下的 Find<name>.cmake 模块,你也可以自己
定义 Find<name>模块,通过 \Textinline{SET(CMAKE_MODULE_PATH dir)}将其放入工程的某个目录
中供工程使用,我们在后面的章节会详细介绍 FIND\_PACKAGE 的使用方法和 Find 模块的
编写。
FIND\_LIBRARY 示例:
\begin{cmake}
FIND_LIBRARY(libX X11 /usr/lib)
IF(NOT libX)
MESSAGE(FATAL\_ERROR “libX not found”)
ENDIF(NOT libX)
\end{cmake}
四,控制指令:
1,IF 指令,基本语法为:
\begin{cmake}
IF(expression)
# THEN section.
COMMAND1(ARGS ...)COMMAND2(ARGS ...)
...
ELSE(expression)
# ELSE section.
COMMAND1(ARGS ...)
COMMAND2(ARGS ...)
...
ENDIF(expression)
\end{cmake}
另外一个指令是 ELSEIF,总体把握一个原则,凡是出现 IF 的地方一定要有对应的
ENDIF.出现 ELSEIF 的地方,ENDIF 是可选的。
表达式的使用方法如下:

IF(var),如果变量不是:空,0,N, NO, OFF, FALSE, NOTFOUND 或
<var>\_NOTFOUND 时,表达式为真。
IF(NOT var ),与上述条件相反。
IF(var1 AND var2),当两个变量都为真是为真。
IF(var1 OR var2),当两个变量其中一个为真时为真。
IF(COMMAND cmd),当给定的 cmd 确实是命令并可以调用是为真。
IF(EXISTS dir)或者 IF(EXISTS file),当目录名或者文件名存在时为真。
IF(file1 IS\_NEWER\_THAN file2),当 file1 比 file2 新,或者 file1/file2 其
中有一个不存在时为真,文件名请使用完整路径。
IF(IS\_DIRECTORY dirname),当 dirname 是目录时,为真。
IF(variable MATCHES regex)
IF(string MATCHES regex)
当给定的变量或者字符串能够匹配正则表达式 regex 时为真。比如:
\begin{cmake}
IF("hello" MATCHES "ell")
MESSAGE("true")
ENDIF("hello" MATCHES "ell")IF(variable LESS number)
IF(string LESS number)
IF(variable GREATER number)
IF(string GREATER number)
IF(variable EQUAL number)
IF(string EQUAL number)
\end{cmake}
数字比较表达式
\begin{cmake}
IF(variable STRLESS string)
IF(string STRLESS string)
IF(variable STRGREATER string)
IF(string STRGREATER string)
IF(variable STREQUAL string)
IF(string STREQUAL string)
\end{cmake}
按照字母序的排列进行比较.
IF(DEFINED variable),如果变量被定义,为真。
一个小例子,用来判断平台差异:
\begin{cmake}
IF(WIN32)
MESSAGE(STATUS “This is windows.”)
#作一些 Windows 相关的操作
ELSE(WIN32)
MESSAGE(STATUS “This is not windows”)
#作一些非 Windows 相关的操作
ENDIF(WIN32)
\end{cmake}
上述代码用来控制在不同的平台进行不同的控制,但是,阅读起来却并不是那么舒服,
ELSE(WIN32)之类的语句很容易引起歧义。
这就用到了我们在 “ 常用变量 ” 一节提到的 

\Textinline{CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS}\\
开关。可以:

\cmakeinline{SET(CMAKE_ALLOW_LOOSE_LOOP_CONSTRUCTS ON)}
这时候就可以写成:
\begin{cmake}
IF(WIN32)
ELSE()
ENDIF() #如果配合 ELSEIF 使用,可能的写法是这样:
IF(WIN32)
#do something related to WIN32
ELSEIF(UNIX)
#do something related to UNIX
ELSEIF(APPLE)
#do something related to APPLE
ENDIF(WIN32)
\end{cmake}
2,WHILE
WHILE 指令的语法是:
\begin{cmake}
WHILE(condition)
COMMAND1(ARGS ...)
COMMAND2(ARGS ...)
...
ENDWHILE(condition)
\end{cmake}
其真假判断条件可以参考 IF 指令。

3,FOREACH
FOREACH 指令的使用方法有三种形式:
\begin{enumerate}
\item 列表
\begin{Textcode}
FOREACH(loop_var arg1 arg2 ...)
COMMAND1(ARGS ...)
COMMAND2(ARGS ...)
...
ENDFOREACH(loop_var)
\end{Textcode}
像我们前面使用的 AUX\_SOURCE\_DIRECTORY 的例子
\begin{cmake}
AUX_SOURCE_DIRECTORY(. SRC_LIST)
FOREACH(F ${SRC_LIST})
MESSAGE(${F})
ENDFOREACH(F)
\end{cmake}
\item 范围
FOREACH(loop\_var RANGE total)
ENDFOREACH(loop\_var)
从 0 到 total 以1为步进举例如下:
\begin{cmake}
FOREACH(VAR RANGE 10)
MESSAGE(${VAR})
\end{cmake}
ENDFOREACH(VAR)
最终得到的输出是:
\begin{bash}
0
1
2
3
4
5
6
7
8
9
10
\end{bash}
\item 范围和步进
FOREACH(loop\_var RANGE start stop [step])
ENDFOREACH(loop\_var)
从 start 开始到 stop 结束,以 step 为步进,
举例如下
\begin{cmake}
FOREACH(A RANGE 5 15 3)
MESSAGE(${A})
ENDFOREACH(A)
\end{cmake}
最终得到的结果是:
\begin{bash}
5
8
11
14
\end{bash}
\end{enumerate}
这个指令需要注意的是,知道遇到 ENDFOREACH 指令,整个语句块才会得到真正的执行。

\textbf{小结:}\\
本小节基本涵盖了常用的 cmake 指令,包括基本指令、查找指令、安装指令以及控制语句
等,特别需要注意的是,在控制语句条件中使用变量,不能用\$\{\}引用,而是直接应用变量
名。掌握了以上的各种控制指令,你应该完全可以通过 cmake 管理复杂的程序了,下一节,我
们将介绍一个比较复杂的例子,通过它来演示本章的一些指令,并介绍模块的概念。
\subsection{复杂的例子:模块的使用和自定义模块}
你现在还会觉得 cmake 简单吗?

本章我们将着重介绍系统预定义的 Find 模块的使用以及自己编写 Find 模块,系统中提供
了其它各种模块,一般情况需要使用 INCLUDE 指令显式的调用,FIND\_PACKAGE 指令是一
个特例,可以直接调用预定义的模块。
其实使用纯粹依靠 cmake 本身提供的基本指令来管理工程是一件非常复杂的事情,所以,
cmake 设计成了可扩展的架构,可以通过编写一些通用的模块来扩展 cmake.
在本章,我们准备首先介绍一下 cmake 提供的 FindCURL 模块的使用。然后,基于我们前
面的 libhello 共享库,编写一个 FindHello.cmake 模块。
\begin{enumerate}
\item 使用 FindCURL 模块
在/backup/cmake 目录建立 t5 目录,用于存放我们的 CURL 的例子。
建立 src 目录,并建立 src/main.c,内容如下:
\begin{cpp}
#include <curl/curl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
FILE *fp;
int write_data(void *ptr, size_t size, size_t nmemb, void *stream)
{
    int written = fwrite(ptr, size, nmemb, (FILE *)fp);
return written;
}
int main()
{
const char * path = “/tmp/curl-test”;
const char * mode = “w”;
fp = fopen(path,mode);
curl_global_init(CURL_GLOBAL_ALL);
CURLcode res;
CURL *curl = curl_easy_init();
curl_easy_setopt(curl, CURLOPT_URL, “http://www.linux-ren.org”);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
}
\end{cpp}
这段代码的作用是通过 curl 取回 www.linux-ren.org 的首页并写入/tmp/curl-test
文件中。
建立主工程文件 CMakeLists.txt
\begin{cmake}
PROJECT(CURLTEST)
ADD_SUBDIRECTORY(src)# 建立 src/CMakeLists.txt
ADD_EXECUTABLE(curltest main.c)
\end{cmake}
现在自然是没办法编译的,我们需要添加 curl 的头文件路径和库文件。
方法 1:

直接通过 INCLUDE\_DIRECTORIES 和 TARGET\_LINK\_LIBRARIES 指令添加:
我们可以直接在 src/CMakeLists.txt 中添加:
INCLUDE\_DIRECTORIES(/usr/include)
TARGET\_LINK\_LIBRARIES(curltest curl)
然后建立 build 目录进行外部构建即可。
现在我们要探讨的是使用 cmake 提供的 FindCURL 模块。

方法 2,

使用 FindCURL 模块。
向 src/CMakeLists.txt 中添加:
\begin{cmake}
FIND_PACKAGE(CURL)
IF(CURL_FOUND)
INCLUDE_DIRECTORIES(\${CURL_INCLUDE_DIR})
TARGET_LINK_LIBRARIES(curltest \${CURL_LIBRARY})
ELSE(CURL_FOUND)
MESSAGE(FATAL_ERROR ”CURL library not found”)
ENDIF(CURL_FOUND)
\end{cmake}
对于系统预定义的 Find<name>.cmake 模块,使用方法一般如上例所示:
每一个模块都会定义以下几个变量
\begin{itemize}
\item \Textinline{<name>_FOUND}
\item \Textinline{<name>_INCLUDE_DIR or <name>_INCLUDES}
\item \Textinline{<name>_LIBRARY or <name>_LIBRARIES}
\end{itemize}
你可以通过<name>\_FOUND 来判断模块是否被找到,如果没有找到,按照工程的需
要关闭某些特性、给出提醒或者中止编译,上面的例子就是报出致命错误并终止构建。
如果<name>\_FOUND 为真,则将<name>\_INCLUDE\_DIR 加入\\
 INCLUDE\_DIRECTORIES,
将<name>\_LIBRARY 加入 TARGET\_LINK\_LIBRARIES 中。
我们再来看一个复杂的例子,通过<name>\_FOUND 来控制工程特性:
\begin{cmake}
SET(mySources viewer.c)
SET(optionalSources)SET(optionalLibs)
FIND_PACKAGE(JPEG)
IF(JPEG_FOUND)
SET(optionalSources ${optionalSources} jpegview.c)
INCLUDE_DIRECTORIES( ${JPEG_INCLUDE_DIR} )
SET(optionalLibs ${optionalLibs} ${JPEG_LIBRARIES} )
ADD_DEFINITIONS(-DENABLE_JPEG_SUPPORT)
ENDIF(JPEG_FOUND)
IF(PNG_FOUND)
SET(optionalSources ${optionalSources} pngview.c)
INCLUDE_DIRECTORIES( ${PNG_INCLUDE_DIR} )
SET(optionalLibs ${optionalLibs} ${PNG_LIBRARIES} )
ADD_DEFINITIONS(-DENABLE_PNG_SUPPORT)
ENDIF(PNG_FOUND)
ADD_EXECUTABLE(viewer ${mySources} ${optionalSources} )
TARGET_LINK_LIBRARIES(viewer ${optionalLibs} 
\end{cmake}
通过判断系统是否提供了 JPEG 库来决定程序是否支持 JPEG 功能。
\item 编写属于自己的 FindHello 模块。
我们在此前的 t3 实例中,演示了构建动态库、静态库的过程并进行了安装。
接下来,我们在 t6 示例中演示如何自定义 FindHELLO 模块并使用这个模块构建工程:
请在建立/backup/cmake/中建立 t6 目录,并在其中建立 cmake 目录用于存放我们自己
定义的 FindHELLO.cmake 模块,同时建立 src 目录,用于存放我们的源文件。
1,定义 cmake/FindHELLO.cmake 模块
\begin{cmake}
FIND_PATH(HELLO_INCLUDE_DIR hello.h /usr/include/hello
/usr/local/include/hello)
FIND_LIBRARY(HELLO_LIBRARY NAMES hello PATH /usr/lib
/usr/local/lib)
IF (HELLO_INCLUDE_DIR AND HELLO_LIBRARY)
SET(HELLO_FOUND TRUE)
ENDIF (HELLO_INCLUDE_DIR AND HELLO_LIBRARY)
IF (HELLO_FOUND)
IF (NOT HELLO_FIND_QUIETLY)
MESSAGE(STATUS "Found Hello: ${HELLO_LIBRARY}")ENDIF (NOT HELLO_FIND_QUIETLY)
ELSE (HELLO_FOUND)
IF (HELLO_FIND_REQUIRED)
MESSAGE(FATAL_ERROR "Could not find hello library")
ENDIF (HELLO_FIND_REQUIRED)
ENDIF (HELLO_FOUND)
\end{cmake}
针对上面的模块让我们再来回顾一下 FIND\_PACKAGE 指令:

FIND\_PACKAGE(<name> [major.minor] [QUIET] [NO\_MODULE]
[[REQUIRED|COMPONENTS] [componets...]])
前面的 CURL 例子中我们使用了最简单的 FIND\_PACKAGE 指令,其实它可以使用多种参数,
QUIET 参数,对应与我们编写的 FindHELLO 中的 HELLO\_FIND\_QUIETLY,如果不指定
这个参数,就会执行:
\Textinline{MESSAGE(STATUS "Found Hello: ${HELLO\_LIBRARY}")}
REQUIRED 参数,其含义是指这个共享库是否是工程必须的,如果使用了这个参数,说明这
个链接库是必备库,如果找不到这个链接库,则工程不能编译。对应于
FindHELLO.cmake 模块中的 HELLO\_FIND\_REQUIRED 变量。
同样,我们在上面的模块中定义了 HELLO\_FOUND,
HELLO\_INCLUDE\_DIR,HELLO\_LIBRARY 变量供开发者在 FIND\_PACKAGE 指令中使用。
OK,下面建立 src/main.c,内容为:
\begin{cpp}
#include <hello.h>
int main()
{
    HelloFunc();
return 0;
}
\end{cpp}
建立 src/CMakeLists.txt 文件,内容如下:
\begin{cmake}
FIND_PACKAGE(HELLO)
IF(HELLO_FOUND)
ADD_EXECUTABLE(hello main.c)
INCLUDE_DIRECTORIES(${HELLO_INCLUDE_DIR})
TARGET_LINK_LIBRARIES(hello ${HELLO_LIBRARY})
ENDIF(HELLO_FOUND)
\end{cmake}
为了能够让工程找到 FindHELLO.cmake 模块(存放在工程中的 cmake 目录)
我们在主工程文件 CMakeLists.txt 中加入:
\Textinline{SET(CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)}
\item 使用自定义的 FindHELLO 模块构建工程
仍然采用外部编译的方式,建立 build 目录,进入目录运行:
\bashinline{cmake ..}
我们可以从输出中看到:
Found Hello: /usr/lib/libhello.so
如果我们把上面的 FIND\_PACKAGE(HELLO)修改为 FIND\_PACKAGE(HELLO QUIET),则
不会看到上面的输出。
接下来就可以使用 make 命令构建工程,运行:
./src/hello 可以得到输出
Hello World。
说明工程成功构建。
\item 如果没有找到 hello library 呢?
我们可以尝试将/usr/lib/libhello.x 移动到/tmp 目录,这样,按照 FindHELLO 模块
的定义,就找不到 hello library 了,我们再来看一下构建结果:
cmake ..
仍然可以成功进行构建,但是这时候是没有办法编译的。
修改 FIND\_PACKAGE(HELLO)为 FIND\_PACKAGE(HELLO REQUIRED),将 hello
library 定义为工程必须的共享库。
这时候再次运行 cmake ..
我们得到如下输出:
CMake Error: Could not find hello library.
因为找不到 libhello.x,所以,整个 Makefile 生成过程被出错中止。
\end{enumerate}
小结:

在本节中,我们学习了如何使用系统提供的 Find<NAME>模块并学习了自己编写
Find<NAME>模块以及如何在工程中使用这些模块。
后面的章节,我们会逐渐学习更多的 cmake 模块使用方法以及用 cmake 来管理 GTK 和 QT4
工程。
